#include "stdafx.h"
#include "WorldBuilder.h"
#include "Material.h"
#include "Texture.h"
#include "TextureTransform.h"


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

Material::Material() 
: m_id(-1), m_manager(NULL), m_instanceContext(NULL)
{
    m_params.makeDefault();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void Material::setTransparent(bool b)
{
    m_params.transparent = b;
}

bool Material::isTransparent() const
{
    return m_params.transparent;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  


void Material::setAmbientColor(const Color& color)
{
    m_params.ambientColor = color;
    notifyManager(MaterialEvent::AmbientColor);
}

void Material::setDiffuseColor(const Color& color)
{
    m_params.diffuseColor = color;
    notifyManager(MaterialEvent::DiffuseColor);
}

void Material::setAmbientAndDiffuseColors(const Color& color)
{
    m_params.ambientColor = color;
    m_params.diffuseColor = color;
    notifyManager(MaterialEvent::AmbientColor
                                    | MaterialEvent::DiffuseColor);
}

void Material::setSpecularColor(const Color& color)
{
    m_params.specularColor = color;
    notifyManager(MaterialEvent::SpecularColor);
}

void Material::setEmissionColor(const Color& color)
{
    m_params.emissionColor = color;
    notifyManager(MaterialEvent::EmissionColor);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

Color Material::getAmbientColor() const
{
    return m_params.ambientColor;
}

Color Material::getDiffuseColor() const
{
    return m_params.diffuseColor;
}

Color Material::getSpecularColor() const
{
    return m_params.specularColor;
}

Color Material::getEmissionColor() const
{
    return m_params.emissionColor;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

float Material::getShininess() const
{
    return m_params.shininess;
}

void Material::setShininess(float newVal)
{
    m_params.shininess = newVal;
    notifyManager(MaterialEvent::Shininess);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

size_t Material::getTextureLayerCount() const
{
    return m_params.textureLayers.size();
}
    
TextureLayer* Material::getTextureLayer(size_t index) const
{
    ASSERT(index >= 0 && index < getTextureLayerCount());
    return m_params.textureLayers[index].get();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void Material::addTextureLayer(ref<Texture> tex, EnvironmentMode envMode, 
                               RgbCombinationMode rgbCombiMode,
                               AlphaCombinationMode alphaCombiMode)
{
    TextureLayer layer;
    layer.setTexture(tex);
    layer.setEnvMode(envMode);
    layer.setRgbCombiMode(rgbCombiMode);
    layer.setAlphaCombiMode(alphaCombiMode);
    layer.setMaterial(this);
    
    m_params.textureLayers.push_back(
        ref<TextureLayer>(new TextureLayer(layer)));
    
    notifyManager(MaterialEvent::TextureLayerAdded);
}

void Material::addTextureLayerAfter(
    TextureLayer* position,	ref<Texture> tex, EnvironmentMode envMode, 
    RgbCombinationMode rgbCombiMode, AlphaCombinationMode alphaCombiMode)
{
    TextureLayer layer;
    layer.setTexture(tex);
    layer.setEnvMode(envMode);
    layer.setRgbCombiMode(rgbCombiMode);
    layer.setAlphaCombiMode(alphaCombiMode);
    layer.setMaterial(this);
    
    TextureLayerList::iterator it = 
        std::find(m_params.textureLayers.begin(), 
                  m_params.textureLayers.end(),
                  position->getRef<TextureLayer>());
    ASSERT(it != m_params.textureLayers.end());
    ++it;

    m_params.textureLayers.insert(
        it, ref<TextureLayer>(new TextureLayer(layer)));
    
    notifyManager(MaterialEvent::TextureLayerAdded);
}

void Material::removeTextureLayer(size_t index)
{
    ASSERT(index < m_params.textureLayers.size());
    m_params.textureLayers.erase(m_params.textureLayers.begin() + index);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void Material::notifyManager(MaterialEvent::Property prop)
{
    if(m_manager != NULL)
    {
        MaterialEvent event_(MaterialEvent::MaterialPropertyChanged, prop);
        event_.material = this;
        m_manager->notifyMaterialAppearanceChanged(event_);
    }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

MaterialParams Material::getParams() const 
{ 
    return m_params; 
}

void Material::setParams(const MaterialParams& newParams) 
{ 
    m_params = newParams;
    notifyManager(MaterialEvent::OtherProperty);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void Material::setPrimaryColorMode(PrimaryColorMode mode)
{
    m_params.primaryColorMode = mode;
    notifyManager(MaterialEvent::OtherProperty);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void Material::setInRenderContext(bool useTextures) const
{
    glPushAttrib(GL_ENABLE_BIT); // Will be popped in "removeFromRenderContext"
    glPushAttrib(GL_TRANSFORM_BIT); // Will be popped in this very func.

    if(m_params.transparent)
    {
        glEnable(GL_BLEND);
    }

    // This is assumed:
    // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 

    glDisable(GL_COLOR_MATERIAL);

    // Shading properties
    if(m_params.primaryColorMode != PrimaryColorModes::DontApply)
    {
        glMaterialfv(GL_FRONT, GL_AMBIENT, m_params.ambientColor.to_array());
        glMaterialfv(GL_FRONT, GL_DIFFUSE, m_params.diffuseColor.to_array());
        glMaterialfv(GL_FRONT, GL_SPECULAR, m_params.specularColor.to_array());
        glMaterialfv(GL_FRONT, GL_EMISSION, m_params.emissionColor.to_array());
        glMateriali(GL_FRONT, GL_SHININESS, int(m_params.shininess * 128.));
    }

    if(useTextures)
    {
        // Apply all texture units associated to this material
        size_t i;
        for(i = 0; i < m_params.textureLayers.size(); ++i)
        {
            //glClientActiveTexture(GL_TEXTURE0 + i);
            glActiveTexture(GL_TEXTURE0 + i);
            glEnable(GL_TEXTURE_2D);
            
            if(m_instanceContext != NULL)
            {
                if(m_instanceContext->size() >= i + 1 && (*m_instanceContext)[i] != NULL)
                {
                    glMatrixMode(GL_TEXTURE);
                    glLoadIdentity();

                    glPushMatrix();

                    double m[16];
                    const ref<TextureTransform>& texXform = (*m_instanceContext)[i];
                    glLoadMatrixd(texXform->getLocalTransform().to_array(m));
                }
            }

            const TextureLayer& layer = *m_params.textureLayers[i].get();
            layer.setInRenderContext();

        }

        // The i>0 is an optimization; if there are not textures, the primary
        // color is applied by default and it makes no difference whether it's
        // applied first or last
        if(i > 0 && m_params.primaryColorMode == PrimaryColorModes::ApplyLast)
        {
            glActiveTexture(GL_TEXTURE0 + i);
            glEnable(GL_TEXTURE_2D);

            // Must bind a texture even if it won't be used
            GLuint texId = TextureManager::getDefaultTexture()->getId();
            glBindTexture(GL_TEXTURE_2D, texId);

            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);

            // Modulate previous fragment color with primary color
            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);

            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);

            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR);
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);

            // Modulate previous fragment alpha with primary alpha
            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);

            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);

            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR);
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
        }
    }

    // Corresponds to the glPushAttrib(GL_ENABLE_BIT) at this very function
    glPopAttrib();

    CHECK_GL_ERRORS();
}

void Material::restoreRenderContext()
{
    glPushAttrib(GL_TRANSFORM_BIT); // Because we're gonna change the matrix mode

    for(size_t i = 0; i < m_params.textureLayers.size(); ++i)
    {
        glActiveTexture(GL_TEXTURE0 + i);

        if(m_instanceContext != NULL)
        {
            if(m_instanceContext->size() >= i + 1 && (*m_instanceContext)[i] != NULL)
            {
                glMatrixMode(GL_TEXTURE);
                glPopMatrix();
                CHECK_GL_ERRORS();
            }
        }

        glDisable(GL_TEXTURE_2D);
    }

    glPopAttrib(); // Restores matrix mode

    // Corresponds to glPushAttrib at beginning of setInRenderContext
    glPopAttrib();

    m_instanceContext = NULL;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

 
void Material::setId(int newId)
{
    m_id = newId;
}

int Material::getId() const
{
    return m_id;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Serialization

void Material::read(std::wistream& is)
{
    Selectable::read(is);
    is >> m_params.ambientColor >>  m_params.diffuseColor
       >> m_params.specularColor >> m_params.shininess;
}

void Material::write(std::wostream& os) const
{
    Selectable::write(os);
    os << m_params.ambientColor << "\n" << m_params.diffuseColor << "\n"
       << m_params.specularColor << "\n" << m_params.shininess << "\n";
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void Material::setInstanceContext(const std::vector<ref<TextureTransform> >* instanceContext)
{
    m_instanceContext = instanceContext;
}

